// file:kernel/interrupt/expection.c
// autor:jiangxinpeng
// time:2021.1.18
// copyright:(C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <os/exception.h>
#include <os/syscall.h>
#include <os/schedule.h>
#include <os/task.h>
#include <os/debug.h>
#include <arch/interrupt.h>
#include <lib/errno.h>

// init exception manager
void ExceptionManagerInit(exception_manager_t *manager)
{
    int i;
    int size = EXC_CODE_MAX / 32; // every byte can store 32 items exceptions

    SpinLockInit(&manager->manager_lock);

    // init manager list
    list_init(&manager->exception_list);
    list_init(&manager->catch_list);

    manager->exception_num = 0;
    manager->catch_num = 0;
    manager->user_mode = 0;
    // init every exception info
    for (i = 0; i < size; i++)
    {
        manager->exception_block[i] = 0;
        manager->exception_catch[i] = 0;
        manager->handler[i] = NULL;
    }
}

// exception manager exit
void ExceptionManagerExit(exception_manager_t *manager)
{
    exception_t *exc;
    uint32_t eflags;

    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    manager->exception_num = 0;
    manager->catch_num = 0;
    // free exception manager exception list
    list_traversal_all_owner_to_next(exc, &manager->exception_list, list)
    {
        // free mem
        KMemFree(exc);
    }
    // free exception manager catch list
    list_traversal_all_owner_to_next(exc, &manager->catch_list, list)
    {
        // free mem
        KMemFree(exc);
    }
    SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
}

// create exception
exception_t *ExceptionCreate(uint32_t code, pid_t source, uint32_t flags)
{
    exception_t *exc = MemAlloc(sizeof(exception_t));
    if (exc != NULL)
    {
        exc->code = code;
        exc->source = source;
        exc->flags = flags;
        list_init(&exc->list);
        return exc;
    }
    return NULL;
}

bool ExceptionFilter(task_t *task, uint32_t code)
{
    if (code != EXC_CODE_CONT)
        return false;
    else
    {
        if (TASK_WAS_STOPPED(task))
        {
            TaskWakeUp(task);
        }
        return true;
    }
}

// only send to exception list
int ExceptionSend(pid_t pid, uint32_t code)
{
    task_t *task = NULL;
    exception_manager_t *manager = NULL;
    exception_t *exception = NULL;
    uint32_t eflags;

    if (code >= EXC_CODE_MAX || pid < 0)
        return -EINVAL;

    // get task by pid
    task = TaskFindByPid(pid);
    if (!task)
        return -EINVAL;

    // get task exception manager
    manager = &task->exception_manager;

    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    if (ExceptionWasBlock(manager, code))
    {
        KPrint("[Eception Send] code %d blocked\n");
        SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
        return -EINVAL;
    }
    // user exception
    // check if exception can be catch
    if (ExceptionCanCatch(manager, code))
    {
        // create exception and add to catch list
        exception = ExceptionCreate(code, pid, 0);
        if (!exception)
        {
            SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
            return -ENOMEM;
        }
        ExceptionAddCatch(manager, exception);
        SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
        TaskWakeUp(task);
        KPrint("[exception] send user exception\n");
        return 0;
    }

    // exception filter
    if (ExceptionFilter(task, code))
    {
        SpinUnlockEnInterrupt(&manager->manager_lock);
        return 0;
    }
    // kernel exception
    // create exception and add to kernel
    exception = ExceptionCreate(code, pid, 0);
    if (!exception)
    {
        SpinUnlockEnInterrupt(&manager->manager_lock);
        return -ENOMEM;
    }
    ExceptionAddKernel(manager, exception);
    KPrint("[exception] send system exception\n");
    SpinUnlockEnInterrupt(&manager->manager_lock);
    return 0;
}

void ExcepetionEnBlock(exception_manager_t *manager, uint32_t code)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return;

    uint32_t eflags;
    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    manager->exception_block[code / 32] |= 1 << (code % 32);
    SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
}

void ExceptionUnBlock(exception_manager_t *manager, uint32_t code)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return;
    uint32_t eflags;
    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    manager->exception_block[code / 32] &= ~(1 << (code % 32));
    SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
}

void ExceptionAddKernel(exception_manager_t *manager, exception_t *exc)
{
    list_add_tail(&exc->list, &manager->exception_list);
    manager->exception_num++;
}

void ExceptionDelKernel(exception_manager_t *manager, exception_t *exc)
{
    if (list_find(&exc->list, &manager->exception_list))
    {
        list_del_init(&exc->list);
        manager->exception_num--;
    }
}

// copy exception from src manager to dest manager
int ExceptionCopy(exception_manager_t *dest, exception_manager_t *src)
{
    exception_t *exc;
    exception_t *tmp;

    // traversal all exception in source exception manager
    list_traversal_all_owner_to_next(exc, &src->exception_list, list)
    {
        // create a new exception according current exception data
        tmp = ExceptionCreate(exc->code, exc->source, exc->flags);
        if (tmp != NULL)
        {
            // add to dest exception manager
            ExceptionAddKernel(dest, tmp);
        }
        else
        {
            // exit dest exception manager
            ExceptionManagerExit(dest);
            return -1;
        }
    }

    // tranversal all catch exception in source exception manager
    list_traversal_all_owner_to_next(exc, &src->exception_list, list)
    {
        tmp = ExceptionCreate(exc->code, exc->source, exc->flags);
        if (tmp != NULL)
        {
            ExceptionAddCatch(dest, exc);
        }
        else
        {
            ExceptionManagerExit(dest);
            return -1;
        }
    }
    return 0;
}

void ExceptionAddCatch(exception_manager_t *manager, exception_t *exception)
{
    list_add_tail(&exception->list, &manager->catch_list);
    manager->catch_num++;
}

void ExceptionDelCatch(exception_manager_t *manager, exception_t *exception)
{
    if (list_find(&exception->list, &manager->catch_list))
    {
        list_del_init(&exception->list);
        manager->catch_num--;
    }
}

void ExceptionEnCatch(uint32_t code, exception_handler_t handler, exception_manager_t *manager)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return;

    uint32_t eflags;
    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    manager->handler[code] = handler;
    SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
}

void ExceptionUnCatch(uint32_t code, exception_manager_t *manager)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return;
    uint32_t eflags;
    SpinLockDisInterruptSave(&manager->manager_lock,eflags);
    manager->handler[code] = NULL;
    SpinUnlockEnableInterruptRestore(&manager->manager_lock,eflags);
}

int ExceptionWasBlock(exception_manager_t *manager, uint32_t code)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return 0;

    return manager->exception_block[code/32]&(1<<code);
}

int ExceptionCanCatch(exception_manager_t *manager, uint32_t code)
{
    if (code == EXC_CODE_FINALHIT || code == EXC_CODE_STOP)
        return 0;

    return (manager->handler[code]) != NULL;
}

int ExceptionForce(pid_t pid, uint32_t code)
{
    task_t *task = TaskFindByPid(pid);
    exception_manager_t *manager;

    if (!task)
        return -ESRCH;
    manager = &task->exception_manager;
    ExceptionUnBlock(manager, code);
    return ExceptionSend(pid, code);
}

int ExceptionForceSelf(uint32_t code)
{
    return ExceptionForce(SysGetPid(), code);
}

int SysExceptionReturn(uint32_t ebx,uint32_t ecx,uint32_t edx,uint32_t esi,uint32_t edi, trap_frame_t *frame)
{
    FpuRestore(&cur_task->fpu);
    ExceptionReturn(frame);
}

// make exception block according status filed
int SysExceptionBlock(uint32_t code, uint32_t status)
{
    if (code >= EXC_CODE_MAX)
        return EINVAL;
    exception_manager_t *manager = &cur_task->exception_manager;

    if (status)
    {
        ExcepetionEnBlock(manager, code);
    }
    else
    {
        ExceptionUnBlock(manager, code);
    }

    return 0;
}

int SysExceptionCatch(uint32_t code, exception_handler_t handler)
{
    exception_manager_t *manager = &cur_task->exception_manager;

    if (code >= EXC_CODE_MAX)
        return EINVAL;

    if (handler != NULL)
    {
        ExceptionEnCatch(code, handler, manager);
    }
    else
    {
        ExceptionUnCatch(code, manager);
    }

    return 0;
}

int SysExceptionSend(pid_t pid, uint32_t code)
{
    return ExceptionSend(pid, code);
}

void *SysExceptionHandler(uint32_t code)
{
    exception_manager_t *manager = &cur_task->exception_manager;
    return manager->handler[code];
}

int SysExceptionMask(uint32_t *mask)
{
    exception_manager_t *manager = &cur_task->exception_manager;
    *mask = manager->exception_block[0];
}

static void ExceptionHandler(exception_manager_t *manager, exception_t *exc, trap_frame_t *frame)
{
    exception_handler_t handler = manager->handler[exc->code];
    if (handler)
    {
        ExceptionFrameBuild(exc->code, handler, frame);
        manager->user_mode = 1;
        FpuSave(&cur_task->fpu);
    }
}

// check and dispatch exception
void ExceptionCheck(trap_frame_t *frame)
{
   // KPrint("check exception");

    ExceptionCheckUser(frame);
    ExceptionCheckKernel(frame);
}

int ExceptionCheckKernel(trap_frame_t *frame)
{
    exception_manager_t *manager = cur_task ? &cur_task->exception_manager : NULL;
    exception_t *exc = NULL, backup = {0};

    if (!manager)
        return -1;
    
    if(manager->exception_num)
        KPrint("[exception] find exception num %d\n",manager->exception_num);

    SpinLockDisInterrupt(&manager->manager_lock);
    if (!manager->exception_num || list_empty(&manager->exception_list))
    {
        SpinUnlockEnInterrupt(&manager->manager_lock);
        return -1;
    }
    list_traversal_all_owner_to_next(exc, &manager->exception_list, list)
    {        
        backup = *exc; // backup exc object
        ExceptionDelKernel(manager, exc);
        ExceptionDisPatch(manager, exc);
        KMemFree(exc);
        exc = &backup; // point backup
    }
    SpinUnlockEnInterrupt(&manager->manager_lock);
    KPrint("check kernel ok\n");
    return 0;
}

int ExceptionCheckUser(trap_frame_t *frame)
{
    exception_manager_t *manager = &cur_task->exception_manager;
    exception_t *exc, backup;

    if (manager->catch_num)
    {
        KPrint("[exception] find user exception num %d\n", manager->catch_num);
    }

    SpinLockDisInterrupt(&manager->manager_lock);
    if (!manager->catch_num || list_empty(&manager->catch_list))
    {
        SpinUnlockEnInterrupt(&manager->manager_lock);
        return -1;
    }

    list_traversal_all_owner_to_next(exc, &manager->catch_list, list)
    {        
        backup = *exc; // backup exc object
        ExceptionDelCatch(manager, exc);
        ExceptionHandler(manager, exc, frame);
        KMemFree(exc);
        exc = &backup; // point backup
    }
    SpinUnlockEnInterrupt(&manager->manager_lock);
    return 0;
}

int ExceptionRaise(uint32_t code)
{
    return ExceptionSend(SysGetPid(), code);
}

// exception dispatch
int ExceptionDisPatch(exception_manager_t *manager, exception_t *exc)
{
    task_t *cur = cur_task;
    switch (exc->code)
    {
    case EXC_CODE_CHLD:
    case EXC_CODE_USER:
    case EXC_CODE_ALRM:
        break;
    case EXC_CODE_STOP:
        cur->exit_status = -exc->code;
        TaskBlock(TASK_STOPPED);
        break;
    case EXC_CODE_TRAP:
        break;
    default:
        Panic("cpu halt beacause exception:%d\n", exc->code);
        break;
    }
    return 0;
}

// send exception to process group
int ExceptionSendGroup(pid_t pgid, uint32_t code)
{
    task_t *task;

    if (pgid < 0)
        return -1;

    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (task->processgroup_id == pgid)
        {
            KPrint("%s: send group exception to %d\n",__func__,task->pid);
            ExceptionSend(task->pid, code);
        }
    }
    return 0;
}

//return 0: no exception cause
//return 1: exit because of exception
bool ExceptionCauseExit(exception_manager_t *manager)
{
    exception_t *exc;

    SpinLock(&manager->manager_lock);
    list_traversal_all_owner_to_next(exc, &manager->exception_list, list)
    {
        if (exc->code != EXC_CODE_CHLD && exc->code != EXC_CODE_USER && exc->code != EXC_CODE_STOP && exc->code != EXC_CODE_CONT && exc->code != EXC_CODE_TRAP && exc->code != EXC_CODE_ALRM)
        {
            SpinUnlock(&manager->manager_lock);
            return true;
        }
    }
    list_traversal_all_owner_to_next(exc, &manager->catch_list, list)
    {
        if (exc->code != EXC_CODE_CHLD && exc->code != EXC_CODE_USER && exc->code != EXC_CODE_STOP && exc->code != EXC_CODE_CONT && exc->code != EXC_CODE_TRAP && exc->code != EXC_CODE_ALRM)
        {
            SpinUnlock(&manager->manager_lock);
            return true;
        }
    }
    SpinUnlock(&manager->manager_lock);
    return false;
}

bool ExceptionCauseExitWhenWait(exception_manager_t *manager)
{
    exception_t *exc;

    SpinLock(&manager->manager_lock);
    list_traversal_all_owner_to_next(exc, &manager->exception_list, list)
    {
        if (exc->code != EXC_CODE_INT)
        {
            SpinUnlock(&manager->manager_lock);
            return true;
        }
    }
    list_traversal_all_owner_to_next(exc, &manager->catch_list, list)
    {
        if (exc->code != EXC_CODE_INT)
        {
            SpinUnlock(&manager->manager_lock);
            return true;
        }
    }
    SpinUnlock(&manager->manager_lock);
    return false;
}
